﻿// ===============================================================================
//  Copyright © NewSQL.CN. All rights reserved .
//  技术论坛：http://newsql.cn/
//  个人博客：http://www.ymind.net/
//  版权所有：NewSQL中文社区 彦铭工作室
// ===============================================================================

using System;
using System.Globalization;
using System.Text;
using System.Text.RegularExpressions;
using MongoDB.Bson;
using MongoDB.Bson.IO;

namespace Newsql.Data
{
	/// <summary>
	/// 封装程序集 <see cref="Newsql.Data"/> 的常用操作。
	/// </summary>
	public static class DataHelper
	{
		private const RegexOptions _REGEX_OPTIONS_COMPILED_SINGLELINE = RegexOptions.Compiled | RegexOptions.Singleline;

		/// <summary>
		/// 返回表示当前对象的 JSON 表示形式。
		/// </summary>
		/// <param name="obj">要序列化的对象。</param>
		/// <param name="indent">指定一个值，该值指示返回值是否允许格式缩进。</param>
		/// <param name="encoding">指定字符编码。</param>
		/// <param name="indentChar">指定格式缩进时使用的字符。</param>
		/// <param name="indentation">指定缩进大小。</param>
		/// <param name="outputMode">指定输出模式。</param>
		/// <returns>返回 <see cref="System.String"/>。</returns>
		public static string ToJson<T>(T obj, bool indent = false, Encoding encoding = null, char indentChar = '\t', int indentation = 1, JsonOptimizationTarget outputMode = JsonOptimizationTarget.Strict)
		{
			if (Equals(obj, default(T))) return String.Empty;

			if (indentChar != ' ' && indentChar != '\t') throw new ArgumentOutOfRangeException("indentChar");

			if (indentation < 1) indentation = 1;
			if (indentation < 8) indentation = 8;

			var settings = JsonWriterSettings.Defaults.Clone();
			settings.Encoding = encoding ?? Encoding.UTF8;
			settings.Indent = indent;
			settings.IndentChars = String.Empty.PadLeft(indentation, indentChar);
			settings.NewLineChars = Environment.NewLine;
			settings.OutputMode = JsonOutputMode.JavaScript;
			settings.GuidRepresentation = GuidRepresentation.CSharpLegacy;

			var json = obj.ToJson(settings);

			return MongoDBJsonCleanup(json, outputMode);
		}

		/// <summary>
		/// 清理 MongoDB 驱动生成的 Json 数据。
		/// </summary>
		/// <param name="json">要清理的 Json 数据。</param>
		/// <param name="outputMode">指定输出模式。</param>
		/// <returns>返回 <see cref="System.String"/>。</returns>
		public static string MongoDBJsonCleanup(string json, JsonOptimizationTarget outputMode = JsonOptimizationTarget.Default)
		{
			if (String.IsNullOrWhiteSpace(json)) throw new ArgumentOutOfRangeException("json");

			var output = json;
			var i = json.IndexOf(@"""_t"" :", StringComparison.Ordinal);

			if (i > 0) // i 的返回值不可能等于0
			{
				var n = json.IndexOf(@""",", i + 5, StringComparison.Ordinal);

				output = n < 0 ? json : json.Remove(i, n + 1);
			}

			switch (outputMode)
			{
				case JsonOptimizationTarget.Default:
				case JsonOptimizationTarget.JavaScript:
					output = _JavaScriptJsonOutputMode(output);
					break;
				case JsonOptimizationTarget.JsonP:
					output = _JavaScriptJsonPOutputMode(output);
					break;
				case JsonOptimizationTarget.Php:
					output = _PhpJsonOutputMode(output);
					break;
				case JsonOptimizationTarget.Strict:
					output = _StrictJsonOutputMode(output);
					break;
			}

			return output;
		}

		private static string _JavaScriptJsonOutputMode(string json)
		{
			if (String.IsNullOrWhiteSpace(json)) throw new ArgumentOutOfRangeException("json");

			var output = json;

			// 日期处理
			var dateRegex = new Regex(@"(new )?Date\((-?\d+)\)", _REGEX_OPTIONS_COMPILED_SINGLELINE);

			foreach (Match match in dateRegex.Matches(output))
			{
				var dateTime = BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(Convert.ToInt64(match.Groups[2].Value));

				output = output.Replace(match.Groups[0].Value, String.Format(@"""{0}""", dateTime.ToUniversalTime().ToString("yyyy-MM-dd HH:mm:ss", CultureInfo.CurrentCulture)));
			}

			return output;
		}

		private static string _JavaScriptJsonPOutputMode(string json)
		{
			if (String.IsNullOrWhiteSpace(json)) throw new ArgumentOutOfRangeException("json");

			// 日期处理
			return Regex.Replace(json, @"(new )?Date\((-?\d+)\)", "new Date($2)", _REGEX_OPTIONS_COMPILED_SINGLELINE);
		}

		private static string _PhpJsonOutputMode(string json)
		{
			if (String.IsNullOrWhiteSpace(json)) throw new ArgumentOutOfRangeException("json");

			var output = json;

			// 日期处理
			var dateRegex = new Regex(@"(new )?Date\((-?\d+)\)", _REGEX_OPTIONS_COMPILED_SINGLELINE);

			foreach (Match match in dateRegex.Matches(output))
			{
				var ticks = Convert.ToInt64(match.Groups[2].Value)*10000;
				var dateSeconds = (ticks < 0 ? (ticks + BsonConstants.UnixEpoch.Ticks) : ticks)/10000000;

				output = output.Replace(match.Groups[0].Value, dateSeconds.ToString(CultureInfo.InvariantCulture));
			}

			return output;
		}

		private static string _StrictJsonOutputMode(string json)
		{
			if (String.IsNullOrWhiteSpace(json)) throw new ArgumentOutOfRangeException("json");

			var output = json;

			// 日期处理
			var dateRegex = new Regex(@"(new )?Date\((-?\d+)\)", _REGEX_OPTIONS_COMPILED_SINGLELINE);

			foreach (Match match in dateRegex.Matches(output))
			{
				var dateTime = BsonUtils.ToDateTimeFromMillisecondsSinceEpoch(Convert.ToInt64(match.Groups[2].Value));

				output = output.Replace(match.Groups[0].Value, String.Format(@"""{0}""", dateTime.ToUniversalTime().ToString("r", CultureInfo.CurrentCulture)));
			}

			return output;
		}
	}
}
